package com.carey.blog.jsonrpc.impl;

import static com.carey.blog.model.Preference.ADMIN_EMAIL;
import static com.carey.blog.model.Preference.BLOG_HOST;
import static com.carey.blog.model.Preference.PREFERENCE;
import static com.carey.blog.model.Preference.TIME_ZONE_ID;

import java.io.File;
import java.io.IOException;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.json.JSONArray;
import org.json.JSONObject;

import com.carey.blog.action.StatusCodes;
import com.carey.blog.framework.latke.Keys;
import com.carey.blog.framework.latke.action.ActionException;
import com.carey.blog.framework.latke.action.util.PageCaches;
import com.carey.blog.framework.latke.repository.Transaction;
import com.carey.blog.framework.latke.util.freemarker.Templates;
import com.carey.blog.jsonrpc.AbstractGAEJSONRpcService;
import com.carey.blog.listener.CareyBlogServletListener;
import com.carey.blog.model.Preference;
import com.carey.blog.model.Skin;
import com.carey.blog.repository.PreferenceRepository;
import com.carey.blog.repository.impl.PreferenceGAERepository;
import com.carey.blog.util.Preferences;
import com.carey.blog.util.Skins;
import com.carey.blog.util.TimeZones;
import com.carey.blog.util.Users;
import com.google.appengine.api.utils.SystemProperty;
import com.google.appengine.api.utils.SystemProperty.Environment.Value;

/**
 * Preference service for JavaScript client.
 * 
 */
public final class PreferenceService extends AbstractGAEJSONRpcService {

	/**
	 * Logger.
	 */
	private static final Logger LOGGER = Logger
			.getLogger(PreferenceService.class.getName());
	/**
	 * Skin utilities.
	 */
	private Skins skins = Skins.getInstance();
	/**
	 * Preference utilities.
	 */
	private Preferences preferenceUtils = Preferences.getInstance();
	/**
	 * Preference repository.
	 */
	private PreferenceRepository preferenceRepository = PreferenceGAERepository
			.getInstance();
	/**
	 * User utilities.
	 */
	private Users userUtils = Users.getInstance();
	/**
	 * Time zone utilities.
	 */
	private TimeZones timeZoneUtils = TimeZones.getInstance();

	/**
	 * Gets signs.
	 * 
	 * @param response
	 *            the specified http servlet response
	 * @return for example,
	 * 
	 *         <pre>
	 * [{
	 *     "oId": "",
	 *     "signHTML": ""
	 *  }, ...]
	 * </pre>
	 * @throws ActionException
	 *             action exception
	 */
	public JSONArray getSigns(final HttpServletResponse response)
			throws ActionException {
		final JSONArray ret = new JSONArray();

		try {
			if (!userUtils.isLoggedIn()) {
				response.sendError(HttpServletResponse.SC_FORBIDDEN);
				return ret;
			}

			final JSONObject preference = preferenceUtils.getPreference();
			final JSONArray allSigns = // includes the empty sign(id=0)
			new JSONArray(preference.getString(Preference.SIGNS));
			for (int i = 1; i < allSigns.length(); i++) { // excludes the empty
															// sign
				ret.put(allSigns.getJSONObject(i));
			}
		} catch (final Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new ActionException(e);
		}

		return ret;
	}

	/**
	 * Gets preference.
	 * 
	 * @param response
	 *            the specified http servlet response
	 * @return for example,
	 * 
	 *         <pre>
	 * {
	 *     "preference": {
	 *         "recentArticleDisplayCount": int,
	 *         "mostUsedTagDisplayCount": int,
	 *         "articleListDisplayCount": int,
	 *         "articleListPaginationWindowSize": int,
	 *         "blogTitle": "",
	 *         "blogSubtitle": "",
	 *         "mostCommentArticleDisplayCount": int,
	 *         "blogHost": "",
	 *         "localeString": "",
	 *         "timeZoneId": "",
	 *         "skinName": "",
	 *         "skinDirName": "",
	 *         "skins": "[{
	 *             "skinName": "",
	 *             "skinDirName": ""
	 *         }, ....]",
	 *         "noticeBoard": "",
	 *         "htmlHead": "",
	 *         "googleOAuthConsumerSecret": "",
	 *         "externalRelevantArticlesDisplayCount": int,
	 *         "relevantArticlesDisplayCount": int,
	 *         "randomArticlesDisplayCount": int,
	 *         "enablePostToBuzz": boolean,
	 *         "adminEmail": "",
	 *         "metaKeywords": "",
	 *         "metaDescription": "",
	 *         "enableArticleUpdateHint": boolean,
	 *         "signs": "[{
	 *             "oId": "",
	 *             "signHTML": ""
	 *         }, ...]"
	 *     }
	 *     "sc": "GET_PREFERENCE_SUCC"
	 * }
	 * </pre>
	 * @throws ActionException
	 *             action exception
	 */
	public JSONObject getPreference(final HttpServletResponse response)
			throws ActionException {
		final JSONObject ret = new JSONObject();

		try {
			if (!userUtils.isAdminLoggedIn()) {
				response.sendError(HttpServletResponse.SC_FORBIDDEN);
				return ret;
			}

			final JSONObject preference = preferenceUtils.getPreference();

			ret.put(PREFERENCE, preference);
			ret.put(Keys.STATUS_CODE, StatusCodes.GET_PREFERENCE_SUCC);
		} catch (final Exception e) {
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new ActionException(e);
		}

		return ret;
	}

	/**
	 * Updates the preference by the specified request json object.
	 * 
	 * @param requestJSONObject
	 *            the specified request json object, for example,
	 * 
	 *            <pre>
	 * {
	 *     "preference": {
	 *         "recentArticleDisplayCount": int,
	 *         "mostUsedTagDisplayCount": int,
	 *         "articleListDisplayCount": int,
	 *         "articleListPaginationWindowSize": int
	 *         "blogTitle": "",
	 *         "blogSubtitle": "",
	 *         "mostCommentArticleDisplayCount": int,
	 *         "skinDirName": "",
	 *         "blogHost": "",
	 *         "localeString": "",
	 *         "timeZoneId": "",
	 *         "noticeBoard": "",
	 *         "htmlHead": "",
	 *         "googleOAuthConsumerSecret": "",
	 *         "externalRelevantArticlesDisplayCount": int,
	 *         "relevantArticlesDisplayCount": int,
	 *         "randomArticlesDisplayCount": int,
	 *         "enablePostToBuzz": boolean,
	 *         "metaKeywords": "",
	 *         "metaDescription": "",
	 *         "enableArticleUpdateHint": boolean,
	 *         "signs": [{
	 *             "oId": "",
	 *             "signHTML": ""
	 *         }, ...]
	 *     }
	 * }, see {@link com.carey.blog.model.Preference} for more details
	 * </pre>
	 * @param request
	 *            the specified http servlet request
	 * @param response
	 *            the specified http servlet response
	 * @return for example,
	 * 
	 *         <pre>
	 * {
	 *     "sc": "UPDATE_PREFERENCE_SUCC"
	 * }
	 * </pre>
	 * @throws ActionException
	 *             action exception
	 * @throws IOException
	 *             io exception
	 */
	public JSONObject updatePreference(final JSONObject requestJSONObject,
			final HttpServletRequest request, final HttpServletResponse response)
			throws ActionException, IOException {
		final JSONObject ret = new JSONObject();
		if (!userUtils.isAdminLoggedIn()) {
			response.sendError(HttpServletResponse.SC_FORBIDDEN);
			return ret;
		}
		final Transaction transaction = preferenceRepository.beginTransaction();
		try {
			final JSONObject preference = requestJSONObject
					.getJSONObject(PREFERENCE);

			if ("valentine".equals(preference.getString(Skin.SKIN_DIR_NAME))) {
				if (!userUtils.hasMultipleUsers()) {
					ret.put(Keys.STATUS_CODE,
							StatusCodes.UPDATE_PREFERENCE_FAIL_NEED_MUL_USERS);
					if (transaction.isActive()) {
						transaction.rollback();
					}

					return ret;
				}
			}

			String blogHost = preference.getString(BLOG_HOST).toLowerCase()
					.trim();
			if (blogHost.startsWith("http://")) {
				blogHost = blogHost.substring("http://".length());
			}
			if (blogHost.endsWith("/")) {
				blogHost = blogHost.substring(0, blogHost.length() - 1);
			}

			LOGGER.log(Level.FINE, "Blog Host[{0}]", blogHost);

			String domain = null;
			final boolean hasPort = blogHost.contains(":");
			if (hasPort) {
				domain = blogHost.split(":")[0].trim();
			} else {
				domain = blogHost;
			}

			final Value gaeEnvValue = SystemProperty.environment.value();
			if (SystemProperty.Environment.Value.Production == gaeEnvValue) {
				if ("localhost".equals(domain)) {
					ret.put(Keys.STATUS_CODE,
							StatusCodes.UPDATE_PREFERENCE_FAIL_CANNT_BE_LOCALHOST);
					if (transaction.isActive()) {
						transaction.rollback();
					}

					return ret;
				}
			}

			String port = "80";
			if (hasPort) {
				port = blogHost.split(":")[1].trim();
			}

			if (!"localhost".equals(domain) && !"80".equals(port)) {
				ret.put(Keys.STATUS_CODE, StatusCodes.UPDATE_PREFERENCE_FAIL_);
				if (transaction.isActive()) {
					transaction.rollback();
				}

				return ret;
			}

			preference.put(BLOG_HOST, blogHost);

			final String skinDirName = preference.getString(Skin.SKIN_DIR_NAME);
			final String skinName = skins.getSkinName(skinDirName);
			preference.put(Skin.SKIN_NAME, skinName);
			final Set<String> skinDirNames = skins.getSkinDirNames();
			final JSONArray skinArray = new JSONArray();
			for (final String dirName : skinDirNames) {
				final JSONObject skin = new JSONObject();
				skinArray.put(skin);

				final String name = skins.getSkinName(dirName);
				skin.put(Skin.SKIN_NAME, name);
				skin.put(Skin.SKIN_DIR_NAME, dirName);
			}
			final String webRootPath = CareyBlogServletListener.getWebRoot();
			final String skinPath = webRootPath + Skin.SKINS + "/"
					+ skinDirName;
			LOGGER.log(Level.FINE, "Skin path[{0}]", skinPath);
			Templates.CACHE.clear();

			preference.put(Skin.SKINS, skinArray.toString());

			final String timeZoneId = preference.getString(TIME_ZONE_ID);
			timeZoneUtils.setTimeZone(timeZoneId);

			preference.put(Preference.SIGNS,
					preference.getJSONArray(Preference.SIGNS).toString());

			final JSONObject oldPreference = preferenceUtils.getPreference();
			final String adminEmail = oldPreference.getString(ADMIN_EMAIL);
			preference.put(ADMIN_EMAIL, adminEmail);

			preferenceUtils.setPreference(preference);

			PageCaches.removeAll();

			transaction.commit();
			ret.put(Keys.STATUS_CODE, StatusCodes.UPDATE_PREFERENCE_SUCC);

			Templates.CONFIGURATION.setDirectoryForTemplateLoading(new File(
					skinPath));
		} catch (final Exception e) {
			if (transaction.isActive()) {
				transaction.rollback();
			}
			LOGGER.log(Level.SEVERE, e.getMessage(), e);
			throw new ActionException(e);
		}

		return ret;
	}

	/**
	 * Gets the {@link PreferenceService} singleton.
	 * 
	 * @return the singleton
	 */
	public static PreferenceService getInstance() {
		return SingletonHolder.SINGLETON;
	}

	/**
	 * Private default constructor.
	 */
	private PreferenceService() {
	}

	/**
	 * Singleton holder.
	 * 
	 */
	private static final class SingletonHolder {

		/**
		 * Singleton.
		 */
		private static final PreferenceService SINGLETON = new PreferenceService();

		/**
		 * Private default constructor.
		 */
		private SingletonHolder() {
		}
	}
}
